/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.tools;

import com.architexa.org.eclipse.draw2d.geometry.Point;
import com.architexa.org.eclipse.gef.AutoexposeHelper;
import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.EditPartViewer;
import com.architexa.org.eclipse.gef.Request;
import com.architexa.org.eclipse.gef.commands.Command;
import com.architexa.org.eclipse.gef.commands.UnexecutableCommand;
import com.architexa.org.eclipse.gef.requests.TargetRequest;

import java.util.Collection;
import java.util.Collections;

import org.eclipse.swt.widgets.Display;



/**
 * The base implementation for tools which perform targeting of editparts.  Targeting
 * tools may operate using either mouse drags or just mouse moves.  Targeting tools work
 * with a <i>target</i> request. This request is used along with the mouse location to
 * obtain an active target from the current EditPartViewer.  This target is then asked for
 * the <code>Command</code> that performs the given request.  The target is also asked to
 * show target feedback.
 * <P>
 * TargetingTool also provides support for auto-expose (a.k.a. auto-scrolling). Subclasses
 * that wish to commence auto-expose can do so by calling {@link
 * #updateAutoexposeHelper()}.  An an AutoExposeHelper is found, auto-scrolling begins.
 * Whenever that helper scrolls the diagram of performs any other change,
 * <code>handleMove</code> will be called as if the mouse had moved. This is because the
 * target has probably moved, but there is no input event to trigger an update of the
 * operation.
 */
public abstract class TargetingTool
	extends AbstractTool
{

private static final int FLAG_LOCK_TARGET = AbstractTool.MAX_FLAG << 1;
private static final int FLAG_TARGET_FEEDBACK = AbstractTool.MAX_FLAG << 2;
/**
 * The max flag.
 */
protected static final int MAX_FLAG = FLAG_TARGET_FEEDBACK;

private Request targetRequest;
private EditPart targetEditPart;
private AutoexposeHelper exposeHelper;

/**
 * Creates the target request that will be used with the target editpart.  This request
 * will be cached and updated as needed.
 * @see #getTargetRequest()
 * @return the new target request
 */
protected Request createTargetRequest() {
	Request request = new Request();
	request.setType(getCommandName());
	return request;
}

/**
 * @see com.architexa.org.eclipse.gef.Tool#deactivate()
 */
public void deactivate() {
	if (isHoverActive())
		resetHover();
	eraseTargetFeedback();
	targetEditPart = null;
	targetRequest = null;
	setAutoexposeHelper(null);
	super.deactivate();
}

/**
 * Called to perform an iteration of the autoexpose process.  If the expose helper is set,
 * it will be asked to step at the current mouse location.  If it returns true, another
 * expose iteration will be queued.  There is no delay between autoexpose events, other
 * than the time required to perform the step().
 */
protected void doAutoexpose() {
	if (exposeHelper == null)
		return;
	if (exposeHelper.step(getLocation())) {
		handleAutoexpose();
		Display.getCurrent().asyncExec(new QueuedAutoexpose());
	} else
		setAutoexposeHelper(null);
}

/**
 * Asks the current target editpart to erase target feedback using the target request.  If
 * target feedback is not being shown, this method does nothing and returns.  Otherwise,
 * the target feedback flag is reset to false, and the target editpart is asked to erase
 * target feedback.  This methods should rarely be overridden.
 */
protected void eraseTargetFeedback() {
	if (!isShowingTargetFeedback())
		return;
	setFlag(FLAG_TARGET_FEEDBACK, false);
	if (getTargetEditPart() != null)
		getTargetEditPart().eraseTargetFeedback(getTargetRequest());
}

/**
 * Queries the target editpart for a command.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getCommand()
 */
protected Command getCommand() {
	if (getTargetEditPart() == null)
		return null;
	return getTargetEditPart().
		getCommand(getTargetRequest());
}

/**
 * Returns a List of objects that should be excluded as potential targets for the
 * operation.
 * @return the list of objects to be excluded as targets
 */
protected Collection getExclusionSet() {
	return Collections.EMPTY_LIST;
}

/**
 * Returns the conditional object used for obtaining the target editpart from the current
 * viewer.  By default, a conditional is returned that tests whether an editpart at the
 * current mouse location indicates a target for the operation's request, using
 * {@link EditPart#getTargetEditPart(Request)}.  If <code>null</code> is returned, then
 * the conditional fails, and the search continues.
 * @see EditPartViewer#findObjectAtExcluding(Point, Collection, 
 * 													EditPartViewer.Conditional)
 * @return the targeting conditional
 */
protected EditPartViewer.Conditional getTargetingConditional() {
	return new EditPartViewer.Conditional() {
		public boolean evaluate(EditPart editpart) {
			return editpart.getTargetEditPart(getTargetRequest()) != null;
		}
	};
}

/**
 * Returns <code>null</code> or the current target editpart.
 * @return <code>null</code> or a target part
 */
protected EditPart getTargetEditPart() {
	return targetEditPart;
}

/**
 * Lazily creates and returns the request used when communicating with the target
 * editpart.
 * @return the target request
 */
protected Request getTargetRequest() {
	if (targetRequest == null)
		setTargetRequest(createTargetRequest());
	return targetRequest;
}

/**
 * This method is called whenever an autoexpose occurs. When an autoexpose occurs, it is
 * possible that everything in the viewer has moved a little. Therefore, by default,
 * {@link AbstractTool#handleMove() handleMove()} is called to simulate the mouse moving
 * even though it didn't.
 */
protected void handleAutoexpose() {
	handleMove();
}

/**
 * Called whenever the target editpart has changed. By default, the target request is
 * updated, and the new target is asked to show feedback.  Subclasses may extend this
 * method if needed.
 * @return <code>true</code>
 */
protected boolean handleEnteredEditPart() {
	updateTargetRequest();
	showTargetFeedback();
	return true;
}

/**
 * Called whenever the target editpart is about to change.  By default, hover is reset, in
 * the case that a hover was showing something, and the target being exited is asked to
 * erase its feedback.
 * @return <code>true</code>
 */
protected boolean handleExitingEditPart() {
	resetHover();
	eraseTargetFeedback();
	return true;
}

/**
 * Called from resetHover() iff hover is active.  Subclasses may extend this method to
 * handle the hover stop event.  Returns <code>true</code> if something was done in
 * response to the call.
 * @see AbstractTool#isHoverActive()
 * @return <code>true</code> if the hover stop is processed in some way
 */
protected boolean handleHoverStop() {
	return false;
}

/**
 * Called when invalid input is encountered.  By default, feedback is erased, and the
 * current command is set to the unexecutable command.  The state does not change, so the
 * caller must set the state to {@link AbstractTool#STATE_INVALID}.
 * @return <code>true</code>
 */
protected boolean handleInvalidInput() {
	eraseTargetFeedback();
	setCurrentCommand(UnexecutableCommand.INSTANCE);
	return true;
}

/**
 * An archaic method name that has been left here to force use of the new name.
 * @throws Exception exc
 */
protected final void handleLeavingEditPart() throws Exception { }

/**
 * Sets the target to <code>null</code>.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleViewerExited()
 */
protected boolean handleViewerExited() {
	setTargetEditPart(null);
	return true;
}

/**
 * Returns <code>true</code> if target feedback is being shown.
 * @return <code>true</code> if showing target feedback
 */
protected boolean isShowingTargetFeedback() {
	return getFlag(FLAG_TARGET_FEEDBACK);
}

/**
 * Return <code>true</code> if the current target is locked.
 * @see #lockTargetEditPart(EditPart)
 * @return <code>true</code> if the target is locked
 */
protected boolean isTargetLocked() {
	return getFlag(FLAG_LOCK_TARGET);
}

/**
 * Locks-in the given editpart as the target. Updating of the target will not occur until
 * {@link #unlockTargetEditPart()} is called.
 * @param editpart the target to be locked-in
 */
protected void lockTargetEditPart(EditPart editpart) {
	if (editpart == null)  {
		unlockTargetEditPart();
		return;
	}
	setFlag(FLAG_LOCK_TARGET, true);
	setTargetEditPart(editpart);
}

/**
 * Extended to reset the target lock flag.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#resetFlags()
 * @see #lockTargetEditPart(EditPart)
 */
protected void resetFlags() {
	setFlag(FLAG_LOCK_TARGET, false);
	super.resetFlags();
}

void resetHover() {
	if (isHoverActive())
		handleHoverStop();
	setHoverActive(false);
}

class QueuedAutoexpose implements Runnable {
	public void run() {
		if (exposeHelper != null)
			doAutoexpose();
	}
}

/**
 * Sets the active autoexpose helper to the given helper, or <code>null</code>.  If the
 * helper is not <code>null</code>, a runnable is queued on the event thread that will
 * trigger a subsequent {@link #doAutoexpose()}.  The helper is typically updated only on
 * a hover event.
 * @param helper the new autoexpose helper or <code>null</code>
 */
protected void setAutoexposeHelper(AutoexposeHelper helper) {
	exposeHelper = helper;
	if (exposeHelper == null)
		return;
	Display.getCurrent().asyncExec(new QueuedAutoexpose());
}

/**
 * Sets the target editpart.  If the target editpart is changing, this method will call
 * {@link #handleExitingEditPart()} for the previous target if not <code>null</code>, and
 * {@link #handleEnteredEditPart()} for the new target, if not <code>null</code>.
 * @param editpart the new target
 */
protected void setTargetEditPart(EditPart editpart) {
	if (editpart != targetEditPart) {
		if (targetEditPart != null)
			handleExitingEditPart();
		targetEditPart = editpart;
		if (getTargetRequest() instanceof TargetRequest)
			((TargetRequest)getTargetRequest()).setTargetEditPart(targetEditPart);
		handleEnteredEditPart();
	}
}

/**
 * Sets the target request. This method is typically not called; subclasses normally
 * override {@link #createTargetRequest()}.
 * @param req the target request
 */
protected void setTargetRequest(Request req) {
	targetRequest = req;
}

/**
 * Asks the target editpart to show target feedback and sets the target feedback flag.
 */
protected void showTargetFeedback() {
	if (getTargetEditPart() != null)
		getTargetEditPart().showTargetFeedback(getTargetRequest());
	setFlag(FLAG_TARGET_FEEDBACK, true);
}

/**
 * Releases the targeting lock, and updates the target in case the mouse is already over a
 * new target.
 */
protected void unlockTargetEditPart() {
	setFlag(FLAG_LOCK_TARGET, false);
	updateTargetUnderMouse();
}

/**
 * Updates the active {@link AutoexposeHelper}. Does nothing if there is still an active
 * helper. Otherwise, obtains a new helper (possible <code>null</code>) at the current
 * mouse location and calls {@link #setAutoexposeHelper(AutoexposeHelper)}.
 */
protected void updateAutoexposeHelper() {
	if (exposeHelper != null)
		return;
	AutoexposeHelper.Search search;
	search = new AutoexposeHelper.Search(getLocation());
	getCurrentViewer()
		.findObjectAtExcluding(getLocation(), Collections.EMPTY_LIST, search);
	setAutoexposeHelper(search.result);
}

/**
 * Subclasses should override to update the target request.
 */
protected void updateTargetRequest() { }

/**
 * Updates the target editpart and returns <code>true</code> if the target changes.  The
 * target is updated by using the target conditional and the target request.  If the
 * target has been locked, this method does nothing and returns <code>false</code>.
 * @return <code>true</code> if the target was changed
 */
protected boolean updateTargetUnderMouse() {
	if (!isTargetLocked()) {
		EditPart editPart = getCurrentViewer().findObjectAtExcluding(
			getLocation(),
			getExclusionSet(),
			getTargetingConditional());
		if (editPart != null)
			editPart = editPart.getTargetEditPart(getTargetRequest());
		boolean changed = getTargetEditPart() != editPart;
		setTargetEditPart(editPart);
		return changed;
	} else
		return false;
}


/**
 * Returns <code>null</code> or the current autoexpose helper.
 * @return null or a helper
 */
protected AutoexposeHelper getAutoexposeHelper() {
    return exposeHelper;
}

}
